package org.example.myRateLimiter;


import lombok.Data;
import org.example.MyMethodInvocation;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

// 漏桶限流器 一个定时方法获取 阻塞队列中的请求执行  定时方法保证了匀速执行
@Data
public class LeakyBucketRateLimiter implements MyRateLimiter {
    private int capacity=5;//  桶的容量 因为有界阻塞队列的存在 这个东西没怎么用到
    private int leakRate=5;// 漏水速率 每秒的请求数

    private final BlockingQueue<MyMethodInvocation> bucket;// 存储请求的队列


    // 构造函数 只有全参构造器 >0才生效 不然为默认的
    public LeakyBucketRateLimiter(int capacity, int leakRate) {
        if (capacity > 0) {
            this.capacity = capacity;
            this.bucket = new ArrayBlockingQueue<>(capacity);
        }
        else {
            this.bucket = new LinkedBlockingQueue<>(5);
        }
        if (leakRate > 0) {
            this.leakRate = leakRate;
        }
        //开启定时任务
        startLeak();

    }


    // 是否允许 看能不能加入成功 能加则允许
    public boolean isAllowed(MyMethodInvocation myMethodInvocation) {
        return  bucket.offer(myMethodInvocation);
    }


    @Override
    public Object tryExecute(MyMethodInvocation myMethodInvocation,MyMethodInvocation fallbackMethodInvocation) {
        //如果 加进去了 则说明将来会执行
        if (isAllowed(myMethodInvocation)) {
            try {
                return myMethodInvocation.getResult();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        //否则返回降级处理的结果
        try {
            return fallbackMethodInvocation.invoke();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    // 定时任务 漏方法执行
    public void startLeak() {
        new Thread(() -> {
            while (true) {
                try {
                    // 每秒漏出一个请求 这里是绝对匀速  如果上下文切换开销大可以调整1s内多少次 相对匀速
                    Thread.sleep(1000 / leakRate);
                    //阻塞的获取请求 因为请求没了会空转
                    MyMethodInvocation myMethodInvocation = bucket.take();
                    Object result = myMethodInvocation.invoke();
                    myMethodInvocation.setResult(result);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }



}
